/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {
    ArkFile,
    AstTreeUtils,
    ts,
} from 'arkanalyzer';
import { Rule } from '../../Index';
import { BaseChecker, BaseMetaData } from '../BaseChecker';
import { Defects } from '../../Index';
import {
    FileMatcher,
    MatcherCallback,
    MatcherTypes,
} from '../../Index';
import { RuleListUtil } from "../../utils/common/DefectsList";
import { IssueReport } from '../../model/Defects';
import { RuleFix } from '../../model/Fix';
type SpaceOption = 'always' | 'never' | 'ignore';
type Options = [
    {
        anonymous: SpaceOption;
        named: SpaceOption;
        asyncArrow: SpaceOption;
    }
];

interface MessageInfo {
    line: number;
    character: number;
    endCol: number;
    message: string;
    node: ts.Node;
    optionType: SpaceOption
}

export class SpaceBeforeFunctionParenCheck implements BaseChecker {
    public issues: IssueReport[] = [];
    public rule: Rule;
    public defects: Defects[] = [];
    public sourceFile: ts.SourceFile;
    private defaultOptions: Options = [
        {
            anonymous: 'always',
            named: 'always',
            asyncArrow: 'always',
        }
    ];

    public metaData: BaseMetaData = {
        severity: 2,
        ruleDocPath: 'docs/space-before-function-paren-check.md',
        description: 'Enforce consistent spacing before function parenthesis',
    };

    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE,
    };

    public registerMatchers(): MatcherCallback[] {
        const matchFileCb: MatcherCallback = {
            matcher: this.fileMatcher,
            callback: this.check
        }
        return [matchFileCb];
    }

    /**
      * 检测函数括号前的空格问题
      * @param code 代码字符串
      * @param options 规则配置选项
      * @returns 错误信息数组，包含行列号和消息
    */
    private checkSpaceBeforeFunctionParen(fileName: string, code: string, options: Options[0]): Array<MessageInfo> {
        const errors: Array<MessageInfo> = [];
        this.sourceFile = AstTreeUtils.getASTNode(fileName, code);

        // 获取AST节点括号位置的统一方法
        const getParenPosition = (node: ts.FunctionLikeDeclaration): number | null => {
            const openParen = node.getChildren().find(c => c.kind === ts.SyntaxKind.OpenParenToken);
            return openParen?.getStart() || null;
        };

        // 遍历AST
        function visitNode(node: ts.Node) {
            if (ts.isFunctionDeclaration(node)) {
                checkNamedFunction(node);
            } else if (ts.isFunctionExpression(node)) {
                checkFunctionExpression(node, !!node.name);
            } else if (ts.isArrowFunction(node)) {
                checkAsyncArrowFunction(node);
            } else if (ts.isMethodDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node)) {
                checkMethod(node);
            } else if (ts.isConstructorDeclaration(node)) {
                checkConstructor(node);
            }
            ts.forEachChild(node, visitNode);
        }

        // 核心检查逻辑（支持注释场景）
        function checkSpace(
            node: ts.Node,
            checkStart: number,
            parenPos: number,
            optionType: "always" | "never" | "ignore",
            errorMessage: string
        ) {
            if (optionType === "ignore" || parenPos === -1) return;

            // 计算实际检查范围（排除注释干扰）
            const spaceCheckStart = Math.max(checkStart, 0);
            const spaceCheckEnd = parenPos - 1;

            // 处理 never 规则（增强换行符和注释处理）
            if (optionType === "never") {
                let hasInvalidSpace = false;
                let pos = spaceCheckStart;
                let firstSpacePos = -1;

                // 遍历从函数名结束到括号前的字符
                while (pos <= spaceCheckEnd) {
                    const char = code[pos];

                    // 检查是否为空白字符（包括换行符、空格等）
                    if (/\s/.test(char)) {
                        if (firstSpacePos === -1) {
                            firstSpacePos = pos;  // 记录第一个空格的位置
                        }
                        hasInvalidSpace = true;
                    }

                    // 处理块注释 /*...*/
                    if (char === '/' && code[pos + 1] === '*') {
                        if (firstSpacePos === -1) {
                            firstSpacePos = pos;  // 如果还没有记录空格位置，就记录注释的位置
                        }
                        hasInvalidSpace = true;
                        const commentEnd = code.indexOf('*/', pos + 2);
                        if (commentEnd === -1) break;
                        pos = commentEnd + 2;
                        continue;
                    }

                    // 处理行注释 //
                    if (char === '/' && code[pos + 1] === '/') {
                        if (firstSpacePos === -1) {
                            firstSpacePos = pos;  // 如果还没有记录空格位置，就记录注释的位置
                        }
                        hasInvalidSpace = true;
                        const lineEnd = code.indexOf('\n', pos);
                        pos = lineEnd === -1 ? spaceCheckEnd + 1 : lineEnd;
                        continue;
                    }

                    pos++;
                }

                // 如果发现任何空白字符（包括换行符）或注释，则在第一个这样的位置报错
                if (hasInvalidSpace && firstSpacePos !== -1) {
                    addError(firstSpacePos, errorMessage, node, optionType);
                }
                return;
            }

            // 优化 always 规则：允许所有空白字符（包括换行符）并跳过注释
            if (optionType === "always") {
                let hasWhitespace = false;
                let pos = spaceCheckStart;

                // 遍历从函数名结束到括号前的字符
                while (pos <= spaceCheckEnd) {
                    const char = code[pos];

                    // 检查是否为空白字符（空格、换行符、制表符等）
                    if (/\s/.test(char)) {
                        hasWhitespace = true;
                        pos++;
                        continue;
                    }

                    // 处理块注释 /*...*/
                    if (char === '/' && code[pos + 1] === '*') {
                        const commentEnd = code.indexOf('*/', pos + 2);
                        if (commentEnd === -1) break;
                        pos = commentEnd + 2;
                        continue;
                    }

                    // 处理行注释 //
                    if (char === '/' && code[pos + 1] === '/') {
                        const lineEnd = code.indexOf('\n', pos);
                        pos = lineEnd === -1 ? spaceCheckEnd + 1 : lineEnd;
                        continue;
                    }

                    // 遇到非空白且非注释字符，退出循环
                    break;
                }

                // 如果整个检查范围内没有空白字符，则报错
                if (!hasWhitespace) {
                    addError(parenPos, errorMessage, node, optionType);
                }
            }
        }

        // 检查命名函数（含泛型声明）
        function checkNamedFunction(node: ts.FunctionDeclaration) {
            if (options.named === "ignore" || !node.name) return;

            // 精确计算检查起点（泛型参数结束位置优先）
            const checkStart = node.typeParameters?.end || node.name.end;
            const parenPos = getParenPosition(node);
            if (!parenPos) return;

            checkSpace(
                node,
                checkStart,
                parenPos,
                options.named,
                options.named === "always"
                    ? "Missing space before function parentheses"
                    : "Unexpected space before function parentheses"
            );
        }

        // 检查异步箭头函数（修复重复报错）
        function checkAsyncArrowFunction(node: ts.ArrowFunction) {
            if (options.asyncArrow === "ignore") return;
            const asyncKeyword = node.modifiers?.find(m => m.kind === ts.SyntaxKind.AsyncKeyword);
            if (!asyncKeyword) return;

            const parenPos = getParenPosition(node);
            if (!parenPos) return;

            // 以 async 关键字结束位置为检查起点
            checkSpace(
                node,
                asyncKeyword.end,
                parenPos,
                options.asyncArrow,
                options.asyncArrow === "always"
                    ? "Missing space before function parentheses"
                    : "Unexpected space before function parentheses"
            );
        }

        // 检查类方法（统一逻辑）
        function checkMethod(node: ts.MethodDeclaration | ts.GetAccessorDeclaration | ts.SetAccessorDeclaration) {
            if (options.named === "ignore") return;

            const checkStart = node.name.end;
            const parenPos = getParenPosition(node);
            if (!parenPos) return;

            checkSpace(
                node,
                checkStart,
                parenPos,
                options.named,
                options.named === "always"
                    ? "Missing space before function parentheses"
                    : "Unexpected space before function parentheses"
            );
        }

        // 检查构造函数（处理泛型）
        function checkConstructor(node: ts.ConstructorDeclaration) {
            if (options.named === "ignore") return;

            const constructorKeyword = node.getChildren().find(c => c.kind === ts.SyntaxKind.ConstructorKeyword);
            if (!constructorKeyword) return;

            const checkStart = node.typeParameters?.end || constructorKeyword.end;
            const parenPos = getParenPosition(node);
            if (!parenPos) return;

            checkSpace(
                node,
                checkStart,
                parenPos,
                options.named,
                options.named === "always"
                    ? "Missing space before function parentheses"
                    : "Unexpected space before function parentheses"
            );
        }

        // 检查函数表达式（匿名/命名）
        function checkFunctionExpression(node: ts.FunctionExpression, isNamed: boolean) {
            const option = isNamed ? options.named : options.anonymous;
            if (option === "ignore") return;

            const checkStart = node.typeParameters?.end
                || (isNamed && node.name?.end)
                || node.getChildren().find(c => c.kind === ts.SyntaxKind.FunctionKeyword)?.end;
            if (!checkStart) return;

            const parenPos = getParenPosition(node);
            if (!parenPos) return;

            checkSpace(
                node,
                checkStart,
                parenPos,
                option,
                option === "always"
                    ? "Missing space before function parentheses"
                    : "Unexpected space before function parentheses"
            );
        }

        // 添加错误信息（行列号转换）
        const addError = (pos: number, message: string, node: ts.Node, optionType: SpaceOption) => {
            const { line, character } = this.sourceFile.getLineAndCharacterOfPosition(pos);
            errors.push({
                line: line + 1,
                character: character + 1,
                endCol: character + node.getText().length + 1,
                message,
                node,
                optionType
            });
        }

        ts.forEachChild(this.sourceFile, visitNode);
        return errors;
    }

    public check = (targetField: ArkFile) => {
        this.defaultOptions = this.getDefaultOption();
        const severity = this.rule.alert ?? this.metaData.severity;
        const filePath = targetField.getFilePath();
        let code = targetField.getCode();
        const myInvalidPositions = this.checkSpaceBeforeFunctionParen(targetField.getName(), code, this.defaultOptions[0]);
        const myInvalidPositionsNew = this.sortMyInvalidPositions(myInvalidPositions);
        myInvalidPositionsNew.forEach(pos => {
            this.addIssueReport(filePath, pos, severity);
        });
    }

    // 对错误位置进行排序并去重
    private sortMyInvalidPositions(myInvalidPositions: Array<MessageInfo>) {
        // 1. 先进行排序
        myInvalidPositions.sort((a, b) => a.line - b.line || a.character - b.character);

        // 2. 使用 reduce 进行去重
        const uniqueArrays = myInvalidPositions.reduce((acc, current) => {
            const lastItem = acc[acc.length - 1];

            // 检查是否与最后一个元素的三要素相同
            if (!lastItem ||
                lastItem.line !== current.line ||
                lastItem.character !== current.character ||
                lastItem.message !== current.message) {
                acc.push(current);
            }
            return acc;
        }, [] as typeof myInvalidPositions);

        return uniqueArrays;
    }

    private getDefaultOption(): Options {
        let option: Options;
        if (this.rule && this.rule.option && this.rule.option[0]) {
            if (typeof this.rule.option[0] === 'string') {
                let optionVal = this.rule.option[0] as SpaceOption;
                return [{ anonymous: optionVal, named: optionVal, asyncArrow: optionVal }];
            } else {
                option = this.rule.option as Options;
                if (!option[0].anonymous) {
                    option[0].anonymous = 'always';
                }
                if (!option[0].named) {
                    option[0].named = 'always';
                }
                if (!option[0].asyncArrow) {
                    option[0].asyncArrow = 'always';
                }
                return option;
            }
        }
        return [{ anonymous: 'always', named: 'always', asyncArrow: 'always' }];
    }

    // 创建修复对象 
    private ruleFix(pos: number, end: number, optionType: SpaceOption): RuleFix {
        let textStr = '';
        if (optionType === 'always') {
            textStr = ' (';
        } else if (optionType === 'never') {
            let textNew = this.sourceFile.getFullText().slice(pos, end);
            if (textNew.includes('\r\n')) {
                textStr = '';
            }else{
                textStr = textNew.trim();
            }
        }
        return { range: [pos, end], text: textStr }
    }

    private addIssueReport(filePath: string, pos: MessageInfo, severity: number) {
        let defect = new Defects(pos.line, pos.character, pos.endCol, pos.message, severity, this.rule.ruleId,
            filePath, this.metaData.ruleDocPath, true, false, false);
        let fix: RuleFix | undefined = undefined;
        let stPos;
        let stEnd;

        if (ts.isFunctionDeclaration(pos.node) || ts.isFunctionExpression(pos.node) || ts.isArrowFunction(pos.node) ||
            ts.isMethodDeclaration(pos.node) || ts.isGetAccessor(pos.node) || ts.isSetAccessor(pos.node) || ts.isConstructorDeclaration(pos.node)) {
            if (pos.optionType == 'always') {
                if (pos.node.parameters.length > 0) {
                    stPos = pos.node.parameters.pos - 1;
                    stEnd = pos.node.parameters.pos;
                } else {
                    stPos = pos.node.parameters.pos - 1;
                    stEnd = pos.node.parameters.end;
                }
                fix = this.ruleFix(stPos, stEnd, pos.optionType);
            } else if (pos.optionType == 'never') {
                if (pos.node.name) {
                    if (pos.node.parameters.length > 0) {
                        stPos = pos.node.name?.end;
                        stEnd = pos.node.parameters.pos - 1;
                    } else {
                        stPos = pos.node.name?.end;
                        stEnd = pos.node.parameters.pos - 1;
                    }
                } else if (pos.node.kind == ts.SyntaxKind.Constructor) {
                    stPos = pos.node.getStart() + 'constructor'.length;
                    stEnd = pos.node.parameters.pos - 1;
                } else if (pos.node.getText().startsWith('async')) {
                    stPos = pos.node.getStart() + 'async'.length;
                    stEnd = pos.node.parameters.pos - 1;
                } else if (pos.node.kind == ts.SyntaxKind.FunctionExpression) {
                    stPos = pos.node.getStart() + 'function'.length;
                    stEnd = pos.node.parameters.pos - 1;
                } else {
                    stPos = pos.node.parameters.pos - 1;
                    stEnd = pos.node.parameters.pos - 1;
                }
                fix = this.ruleFix(stPos, stEnd, pos.optionType);
            }
        }

        this.issues.push(new IssueReport(defect, fix));
        RuleListUtil.push(defect);
    }
}